package com.zxd.interview.mycache.version4;

import com.zxd.interview.mycache.compute.ComputeAble;
import com.zxd.interview.mycache.compute.ExpensiveCompute;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.*;

/**
 * 第四个版本，方法改为泛型实现
 * @author
 * @version v1.0.0
 * @Package : com.zxd.interview.mycache.version3
 * @Description : 第三个版本
 * @Create on : 2023/6/15 16:47
 **/
@Slf4j
public class MyCacheVersion4<A, V> {
    /**
     * 改造，并发不安全集合改为并发安全集合
     * value 存储为 future的值
     */
    private final Map<A, Future<V>> concurrentHashMap = new ConcurrentHashMap<>();

    private final ComputeAble computeAble = new ExpensiveCompute();

    /**
     * 先使用具体类型实现，后续改为使用泛型实现
     * 1. 使用 FutureTask 对于要计算的值进行封装，根据 FutureTask 特性，获取到结果之前单个线程会一直等待
     * 2. 由于计算方法变动，所有的代码需要调整
     * 3. concurrentHashMap.get() 在 if 判断的时候依然存在非原子行为，所以在设置的时候使用 putIfAbsent 原子操作
     * 4. 重构，使用泛型参数
     * @param arg
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    public V compute(A arg) throws InterruptedException, ExecutionException {
        Future<V> result = concurrentHashMap.get(arg);
        // 如果获取不到内容，说明不在缓存当中
        if(Objects.isNull(result)){
            // 此时利用callAble 线程任务指定任务获取，在获取到结果之前线程会阻塞
            FutureTask<V> future = new FutureTask<>(new Callable<V>() {
                @Override
                @SuppressWarnings("unchecked")
                public V call() throws Exception {
                    return (V) computeAble.doCompute(arg);
                }
            });
            //把新的future覆盖之前获取的future
            result = future;
            // 执行
            future.run();
            log.info("FutureTask 调用计算函数");
            result = concurrentHashMap.putIfAbsent(arg, result);
            // 如果返回null，说明这个记录被添加过了
            if(Objects.isNull(result)){
                log.info("其他线程进行设置,重新执行计算");
                // 说明其他线程已经设置过值，这里重新跑一次计算方法即可直接获取
                result = future;
                // 再重新跑一次
                future.run();
                return result.get();
            }else{
                return result.get();
            }
        }
        return result.get();
    }
}
